#!/bin/sh /etc/rc.common

START=90
SERVICE_USE_PID=1
SERVICE_WRITE_PID=1
SERVICE_DAEMONIZE=1
EXTRA_COMMANDS="firewall hotplug switch restart"

APP='redsocks2'
BIN='/usr/bin/redsocks2'
CONF='/var/etc/redsocks2.conf'
SHADOWSOCKS_SERVER_LIST='/etc/shadowsocks.list'
IP_IGNORE_CHINA='/etc/ipset/china'
IP_IGNORE_USER='/etc/ipset/ignore'
NS_SERVER='114.114.114.114'
LOCAL_IP='0.0.0.0'
DNS_DEFAULT_PORT='53'
IP_RETURN='0.0.0.0/8,10.0.0.0/8,100.64.0.0/10,127.0.0.0/8,169.254.0.0/16,172.16.0.0/12,192.0.0.0/24,192.0.2.0/24,192.88.99.0/24,192.168.0.0/16,198.18.0.0/15,198.51.100.0/24,203.0.113.0/24,224.0.0.0/4,240.0.0.0/4,255.255.255.255'

conf_base() {
[ -z "$CONF_FILE" ] && return 1
if [ "$log" = '1' ]; then
	LOG_FILE="file:$LOG_FILE"
else
	LOG_FILE='file:/dev/null'
fi
if [ "$LOG_DEBUG" = '1' ]; then
	LOG_DEBUG='on'
else
	LOG_DEBUG='off'
fi
if [ "$LOG_INFO" = '1' ]; then
	LOG_INFO='on'
else
	LOG_INFO='off'
fi
CONF_FILE="$CONF_FILE
base {
	log_debug = $LOG_DEBUG;
	log_info = $LOG_INFO;
	log = \"$LOG_FILE\";
	daemon = off;
	redirector = iptables;
}"
}

conf_ipcache() {
[ -z "$CONF_FILE" ] && return 1
CONF_FILE="$CONF_FILE
ipcache {
	cache_size = $ipcache_size;
	cache_file = \"$ipcache_file\";
	stale_time = $ipcache_stale_time;
	autosave_interval = $ipcache_autosave_interval;
	port_check = $ipcache_port_check;
}
autoproxy {
	no_quick_check_seconds = $ipcache_no_quick_check_seconds;
	quick_connect_timeout = $ipcache_quick_connect_timeout;
}"
}

conf_head() {
if [ "$1" = 'udp' ]; then
	echo "redudp {
	local_ip = $LOCAL_IP;
	local_port = $UDP_LOCAL_PORT;
	dest_ip = $UDP_DEST_IP;
	dest_port = $UDP_DEST_PORT;
	udp_timeout = $udp_timeout;
	type = $udp_proxy_type;"
else
	echo "redsocks {
	local_ip = $LOCAL_IP;
	local_port = $local_port;
	autoproxy = $auto_proxy;
	timeout = $timeout;
	type = $proxy_type;"
fi
}

conf_rules() {
[ "$1" = 'udp' ] || IPTABLES_RULES_PROXY=$IPTABLES_RULES_PROXY"-A REDSOCKS2_WAN -p tcp -j REDIRECT --to-ports $local_port\n"
}

conf_socks5() {
[ -z "$socks5_ip" -o -z "$socks5_port" ] && return 1
CONF_FILE="$CONF_FILE
$(conf_head $1)
	ip = $socks5_ip;
	port = $socks5_port;$([ -z "$proxy_login" ] || echo "
	login = $proxy_login;" )$([ -z "$proxy_password" ] || echo "
	password = \"$proxy_password\";")
}"
host_check "$socks5_ip"
conf_rules $1
}

conf_shadowsocks() {
[ -z "$shadowsocks_ip" -o -z "$shadowsocks_port" -o -z "$shadowsocks_encrypt_method" -o -z "$shadowsocks_password" ] && return 1
CONF_FILE="$CONF_FILE
$(conf_head $1)
	ip = $shadowsocks_ip;
	port = $shadowsocks_port;
	login = $shadowsocks_encrypt_method;
	password = \"$shadowsocks_password\";
}"
host_check "$shadowsocks_ip"
conf_rules $1
}

conf_vpn() {
[ -z "$vpn_interface" ] && return 1
CONF_FILE="$CONF_FILE
$(conf_head $1)
	interface = $vpn_interface;
}"
conf_rules $1
}

conf_goagent() {
[ -z "$goagent_ip" -o -z "$goagent_port" ] && return 1
local_port_https=$(($local_port+1))
CONF_FILE="$CONF_FILE
redsocks {
	local_ip = $LOCAL_IP;
	local_port = $local_port;
	autoproxy = $auto_proxy;
	timeout = $timeout;
	type = http-relay;
	ip = $goagent_ip;
	port = $goagent_port;
}
redsocks {
	local_ip = $LOCAL_IP;
	local_port = $local_port_https;
	autoproxy = $auto_proxy;
	timeout = $timeout;
	type = http-connect;
	ip = $goagent_ip;
	port = $goagent_port;
}"
IPTABLES_RULES_PROXY=$IPTABLES_RULES_PROXY"-A REDSOCKS2_WAN -p tcp --dport 80 -j REDIRECT --to-ports $local_port\n-A REDSOCKS2_WAN -p tcp --dport 443 -j REDIRECT --to-ports $local_port_https\n"
}

conf_udp() {
for LINE in $udp_dest_list
do
	UDP_LOCAL_PORT=${LINE##*\#}
	UDP_DEST=${LINE%%#*}
	UDP_DEST_IP=${UDP_DEST%%:*}
	UDP_DEST_PORT=${UDP_DEST##*:}
	if [ -z "$UDP_LOCAL_PORT" -o -z "$UDP_DEST_IP" -o -z "$UDP_DEST_PORT" ]; then
		echo "\"$LINE\" is not a valid format for configing UDP."
	else
		case $udp_proxy_type in
			socks5)
				conf_socks5 'udp'
				;;
			shadowsocks)
				conf_shadowsocks 'udp'
				;;
			vpn)
				conf_vpn 'udp'
				;;
		esac
	fi
done
}

conf_tcp_dns() {
[ -z "$tcp_dns_timeout" -o -z "$tcp_dns_local_port" ] && return 1
[ -z "$tcp_dNS_SERVER1" -a "$tcp_dNS_SERVER2" ] && return 1
[ -z "$tcp_dNS_SERVER1" -a -z "$tcp_dNS_SERVER2" ] && return 1
CONF_FILE="$CONF_FILE
tcpdns {
	local_ip = $LOCAL_IP;
	local_port = $tcp_dns_local_port;
	timeout = $tcp_dns_timeout;$([ -z "$tcp_dNS_SERVER1" ] || echo "
	tcpdns1 = $tcp_dNS_SERVER1;")$([ -z "$tcp_dNS_SERVER2" ] || echo "
	tcpdns2 = $tcp_dNS_SERVER2;")
}"
}

conf_create() {
	[ -z "$CONF_FILE" ] && return 1
	mkdir -p /var/etc
	echo -e "$CONF_FILE">"$CONF"
}

check_if_ip() {
	tmp_ip=$(echo $1|grep -o '\([0-9]/{1,3/}\.\)\{3\}[0-9]/{1,3/}')
	[ -z "$tmp_ip" ] || {
		echo "$tmp_ip"
		return 0
	}
	return 1
}

resolve_ip() {
	[ -z "$1" ] && return 1
	# SERVER_IPS=$( nslookup "$1" "$NS_SERVER" | awk 'NR>=5{print $3}' )
	if ( command -v resolveip &>/dev/null ); then
		IP_SERVERS=$( resolveip -4 -t 10 "$1" )
	else
		IP_SERVERS=$( nslookup "$1" "$NS_SERVER" | awk 'NR>=5{print $3}' )
	fi
	[ -z "$IP_SERVERS" ] || echo -e "$IP_SERVERS"
}

host_check() {
	[ -z "$1" ] && return 1
	echo "Proxy Server: $1"
	if ( echo "$1" | grep -q '\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}' ); then
		IP_SERVERS="$1"
		IP_SERVER="$1"
	else
		resolve_ip "$1"
		if [ -z "$SERVER_IPS" ]; then
			echo "Resolve host ip ... Failed"
			return 1
		else
			IP_SERVER=$( echo -e "$SERVER_IPS" | head -n 1 )
			echo "Resolve host ip ... $1"
		fi
	fi
	return 0	
}

check_prerouting() {
	if iptables -t nat --list zone_lan_prerouting &>/dev/null; then
		PREROUTING='zone_lan_prerouting'
	else
		PREROUTING='PREROUTING'
	fi
}

iptables_flush() {
	iptables-restore <<-EOF || {
		$(iptables-save -t nat|grep -v 'REDSOCKS2')
	EOF
		[ -z "$PREROUTING" ] && check_prerouting
		iptables -t nat -F REDSOCKS2_LAN
		iptables -t nat -F REDSOCKS2_WAN
		iptables -t nat -D $PREROUTING -p tcp -j REDSOCKS2_LAN
		iptables -t nat -D OUTPUT -p tcp -j REDSOCKS2_WAN
		iptables -t nat -X REDSOCKS2_LAN
		iptables -t nat -X REDSOCKS2_WAN
	}
	return $?
}

iptables_restore() {
	iptables-restore -n <<-EOF
		$(echo -e "$1")
	EOF
	return $?
}

ipset_flush() {
	ipset destroy "$APP" &>/dev/null
}

ipset_restore() {
	ipset version &>/dev/null || return 1
	ipset_flush
	ipset -! -R <<-EOF
		create $APP hash:net
		$(echo -e "$1" | sed -e "s/^/add $APP /")
	EOF
	return $?
}

rules() {
	[ -z "$IPTABLES_RULES_PROXY" ] && return 1
	[ -z "$PREROUTING" ] && check_prerouting

	IP_RETURN="$(echo $IP_RETURN | tr ',' '\n')\n"$IP_SERVERS
	[ -f "$SHADOWSOCKS_SERVER_LIST" ] && {
		IP_RETURN_SERVER=$(cat $SHADOWSOCKS_SERVER_LIST | grep -o '\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}')
		[ -z "$IP_RETURN_SERVER" ] || IP_RETURN=$IP_RETURN"\n$IP_RETURN_SERVER"
	}

	IPTABLES_RULES="*nat\n:REDSOCKS2_WAN - [0:0]\n:REDSOCKS2_LAN - [0:0]\n"

	config_get lan_control_mode config lan_control_mode '0'
	[ "$lan_control_mode" = '0' ] || IP_LAN=$(config_list_foreach config lan_ip echo)
	case $lan_control_mode in
		0)
			echo "LAN Control ... Disabled"
			IPTABLES_RULES=$IPTABLES_RULES"-A REDSOCKS2_LAN -p tcp -j REDSOCKS2_WAN\n"
			;;
		1)
			echo "LAN Control ... Allow listed only"
			[ -z "$IP_LAN" ] || IPTABLES_RULES_LAN=$(echo -e "$IP_LAN" | sed "s/\(.*\)/-A REDSOCKS2_LAN -s \1 -p tcp -j REDSOCKS2_WAN/")
			[ -z "$IPTABLES_RULES_LAN" ] || IPTABLES_RULES=$IPTABLES_RULES$IPTABLES_RULES_LAN"\n"
			IPTABLES_RULES=$IPTABLES_RULES"-A REDSOCKS2_LAN -p tcp -j RETURN\n"
			;;
		2)
			echo "LAN Control ... Allow all except listed"
			[ -z "$IP_LAN" ] || IPTABLES_RULES_LAN=$(echo -e "$IP_LAN" | sed "s/\(.*\)/-A REDSOCKS2_LAN -s \1 -j RETURN/")
			[ -z "$IPTABLES_RULES_LAN" ] || IPTABLES_RULES=$IPTABLES_RULES$IPTABLES_RULES_LAN"\n"
			IPTABLES_RULES=$IPTABLES_RULES"-A REDSOCKS2_LAN -p tcp -j REDSOCKS2_WAN\n"
			;;
	esac

	[ -z "$IP_RETURN" ] || IPTABLES_RULES=$IPTABLES_RULES$(echo -e "$IP_RETURN" | sed "s/\(.*\)/-A REDSOCKS2_WAN -d \1 -j RETURN/")"\n"

	config_get ignore_china config ignore_china '0'
	IP_RETURN_LIST=$([ "$ignore_china" = '1' ] && cat "$IP_IGNORE_CHINA")"\n"$(cat "$IP_IGNORE_USER")
	IP_RETURN_LIST=$(echo -e "$IP_RETURN_LIST"|grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?')
	[ -z "$IP_RETURN_LIST" ] || {
		if ipset_restore "$IP_RETURN_LIST"; then
			IPTABLES_RULES=$IPTABLES_RULES"-A REDSOCKS2_WAN -p tcp -m set --match-set $APP dst -j RETURN\n"
		else
			IPTABLES_RULES=$IPTABLES_RULES$(echo -e "$IP_RETURN_LIST" | sed "s/\(.*\)/-A REDSOCKS2_WAN -d \1 -j RETURN/")"\n"
		fi
	}
	IPTABLES_RULES=$IPTABLES_RULES$IPTABLES_RULES_PROXY
	IPTABLES_RULES=$IPTABLES_RULES"-A OUTPUT -p tcp -o lo -j REDSOCKS2_WAN\n-A $PREROUTING -p tcp -j REDSOCKS2_LAN\nCOMMIT"

	if iptables_restore "$IPTABLES_RULES"; then
		echo "Setting ... OK"
	else
		echo "Setting ... Failed"
		stop
	fi
}

service_options() {
	OPTIONS="-c $CONF"
	config_load "$APP"
	config_get enabled config enabled
	[ "$enabled" = '1' ] && {
		config_get local_port config local_port '1080'
		config_get auto_proxy config auto_proxy '1'
		config_get timeout config timeout '3'
		config_get proxy_type config proxy_type
		case $proxy_type in
			socks5)
				config_get socks5_ip config socks5_ip
				config_get socks5_port config socks5_port
				config_get socks5_username config socks5_username
				config_get socks5_password config socks5_password
				conf_socks5
				;;
			shadowsocks)
				config_get shadowsocks_ip config shadowsocks_ip
				config_get shadowsocks_port config shadowsocks_port
				config_get shadowsocks_encrypt_method config shadowsocks_encrypt_method
				config_get shadowsocks_password config shadowsocks_password
				conf_shadowsocks
				;;
			vpn)
				config_get vpn_interface config vpn_interface
				conf_vpn
				;;
			goagent)
				config_get goagent_ip config goagent_ip
				config_get goagent_port goagent goagent_port
				conf_goagent
				;;
		esac
		config_get ipcache config ipcache
		[ "$ipcache" = '1' ] && {
			config_get ipcache_size config ipcache_size '4'
			config_get ipcache_file config ipcache_file '/tmp/ipcache.txt'
			config_get ipcache_stale_time config ipcache_stale_time '7200'
			config_get ipcache_autosave_interval config ipcache_autosave_interval '3600'
			config_get ipcache_port_check config ipcache_port_check '0'
			config_get ipcache_no_quick_check_seconds config ipcache_no_quick_check_seconds '300'
			config_get ipcache_quick_connect_timeout config ipcache_quick_connect_timeout '0'
			conf_ipcache
		}
	}

	config_get udp config udp
	[ "$udp" = '1' ] && {
		config_get udp_timeout config udp_timeout '3'
		config_get udp_proxy_type config udp_proxy_type
		case $udp_proxy_type in
			socks5)
				config_get udp_socks5_ip config udp_socks5_ip
				config_get udp_socks5_port config udp_socks5_port
				config_get udp_socks5_username config udp_socks5_username
				config_get udp_socks5_password config udp_socks5_password
				socks5_ip=$udp_socks5_ip;socks5_port=$udp_socks5_port;socks5_username=$udp_socks5_username;socks5_password=$udp_socks5_password
				;;
			shadowsocks)
				config_get udp_shadowsocks_ip config udp_shadowsocks_ip
				config_get udp_shadowsocks_port config udp_shadowsocks_port
				config_get udp_shadowsocks_encrypt_method config udp_shadowsocks_encrypt_method
				config_get udp_shadowsocks_password config udp_shadowsocks_password
				shadowsocks_ip=$udp_shadowsocks_ip;shadowsocks_port=$udp_shadowsocks_port;shadowsocks_encrypt_method=$udp_shadowsocks_encrypt_method;shadowsocks_password=$udp_shadowsocks_password
				;;
			vpn)
				config_get udp_vpn_interface config udp_vpn_interface
				vpn_interface=$udp_vpn_interface
				;;
		esac
		udp_dest_list=$( config_list_foreach config udp_dest echo )
		[ -z "$udp_dest_list" ] || conf_udp
	}

	config_get tcp_dns config tcp_dns
	[ "$tcp_dns" = '1' ] && {
		config_get tcp_dns_local_port config tcp_dns_local_port
		config_get tcp_dns_timeout config tcp_dns_timeout
		config_get tcp_dns_server1 config tcp_dns_server1
		config_get tcp_dns_server2 config tcp_dns_server2
		conf_tcp_dns
	}

	config_get log config log '0'
	config_get log_debug config log_debug '0'
	config_get log_info config log_info '0'
	config_get log_file config log_file '/tmp/redsocks2.log'
	conf_base
}

service_check() {
	FIND_DATA=$(ps|grep "$1"|grep -v "grep $1")
	[ -z "$FIND_DATA" ] && return 1
	return 0
}

service_process() {
	service_check "$1" && echo "Start $1 ... Already running. Please try \"stop\" or \"restart\"" && return 1
	service_start $1 $2
	service_check "$1" && echo "Start $1 ... OK" && return 0
	echo "Start $1 ... Fail" && return 1
}

start() {
	service_options
	conf_create && service_process "$BIN" "$OPTIONS" && rules
}

stop() {
	iptables_flush
	ipset_flush
	service_stop $BIN
}

firewall() {
	restart
}

hotplug() {
	restart
}

switch() {
	[ -z "$1" -o -z "$2" ] && return 1
	killall -q -9 ${BIN##*/} &>/dev/null && service_start $1 $2
}

